package TrainModel;

import application.Main;

import application.MapElement;

import java.lang.StringBuilder;

import TrainController.*;
import TrackModel.*;
import Utilities.Message;
import Utilities.TrainConstants;

public class TrainModel implements TrainControllerToTrainModel, TrackModelToTrainModel, TrainModelUI
{
	public MapElement mapElement;
	
	private int TrainId;
	private TrainCar CarMod;
	private CommunicationModule commMod;
	private PhysicsEngine physicsEng;
	
	//private boolean bPhysicsDoneThisTick;
	private double currVelocity; // in ft/s
	private long lElapsedTime;
	
	//private String sStation;
	
	public TrainController trainControl;
	private Block currentBlock;
	private long lTickLen;
	private boolean bTransponderFailure;
	private String sLastTransponderSignal;
	private String header;
	
	public TrainModel( int TrainId, long tickL )
	{
		this.TrainId = TrainId;
		// Register with main
		Main.registerTrainModel(this);
		
		header = new String("Train " + TrainId + ": TrainModel: ");
		Main.log.write(header + "I am a Train Model");
		
		CarMod = new TrainCar(Main.log, TrainId);
		
		// Instantiate CommMod
		commMod = new CommunicationModule(Main.log, TrainId);
		
		// Instantiate Physics
		physicsEng = new PhysicsEngine(Main.log, TrainId);
		
		trainControl = new TrainController(TrainId, this, tickL);
		
		currVelocity = 0.0;
		lTickLen = tickL;
		
		lElapsedTime = 0;
		bTransponderFailure = false;
		sLastTransponderSignal = null;
	}
	
	// TODO: Add this to GUI
	// Returns the thermostat in the Car Module
	public double getThermostat()
	{
		return CarMod.getThermostat();
	}
	
	// TODO: Add this to GUI
	// Sets the thermostat in the Car Module
	public void setThermostat( double t )
	{
		CarMod.setThermostat(t);
	}
	
	public void setCurrentBlock( Block b )
	{
		currentBlock = b;
	}
	
	public Block getCurrentBlock()
	{
		return currentBlock;
	}
	
	public void setTransponderSignal( String s )
	{
		if( !this.bTransponderFailure )
		{
			Main.log.write(header + "Transponder Signal received: " + s);
			trainControl.arrival( s );
			sLastTransponderSignal = s;
		}
		return;
	}
	
	public void tick()
	{
		lElapsedTime += lTickLen;
		
		// Receive messages from Track Model
		Message currMess = commMod.getMessage();
		
		// Pass messages to Train Controller
		if( currMess != null)
		{
			// Pass message to TrainController
			trainControl.signalPickup(currMess);
		}
		
		CarMod.run();
		
		// Run physics for this tick
		double currWeight = CarMod.getWeight();
		double grade = currentBlock.getGrade()/100;
		//double grade = 0;
		
		// TODO: Re enable this
		double dFrictionStatic = currentBlock.getFrictionCoeffStatic();
		//double dFrictionStatic = TrainConstants.dWheelToTrackFrictionCoeffStatic;
		physicsEng.setFrictionCoeffStatic(dFrictionStatic);
		double dFrictionKinetic = currentBlock.getFrictionCoeffKinetic();
		//double dFrictionKinetic = TrainConstants.dWheelToTrackFrictionCoeffKinetic;
		physicsEng.setFrictionCoeffKinetic(dFrictionKinetic);
		currVelocity =  physicsEng.run(((double)lTickLen)/1000, currWeight, grade);
		
		// Pass faults to Train Controller
		if( physicsEng.getEngineFail() )
		{
			trainControl.reportEngineFailure();
		}
		
		if( physicsEng.getBrakeFail() )
		{
			trainControl.reportBrakeFailure();
		}
		
		if( commMod.getSignalPickupFailure() )
		{
			trainControl.reportSignalFailure();
		}
	}
	
	public void setMessage(Message m)
	{
		commMod.recieveMessage(m);
	}
	
	public int getNumPassengers()
	{
		return CarMod.getNumPassengers();
	}
	
	public boolean getWheelSlip()
	{
		return physicsEng.bWheelSlip;
	}
	
	public double getSpeed()
	{
		return (currVelocity*0.3048); // converting from ft/s to m/s
	}
	
	public boolean setPassengerFlow( int iNumberOfPassengers )
	{
		CarMod.setPassengerFlow(iNumberOfPassengers);
		return true;
	}
	
	public double calcWeight()
	{
		return CarMod.getWeight();
	}
	
	public void toggleConductor()
	{
		if( CarMod.bConductorOnBoard)
		{
			CarMod.removeConductor();
		}
		else
		{
			CarMod.loadConductor();
		}
	}
	
	public void setPower( double dPower )
	{
		if( CarMod.bConductorOnBoard )
		{
			physicsEng.setPower(dPower);
		}
		else
		{
			Main.log.write("Train " + TrainId + ": TrainModel: Tried to set power without a conductor. BAD!");
		}
	}
	
	public void setBrake( double dBrakePercent )
	{
		if( CarMod.bConductorOnBoard )
		{
			physicsEng.setBrake(dBrakePercent);
		}
	}
	
	public void setEBrake()
	{
		physicsEng.setEBrake();
	}
	
	public void setDoorOpen( boolean bDoorState )
	{
		if( bDoorState )
		{
			CarMod.openDoors();
		}
		else
		{
			CarMod.closeDoors();
		}
	}
	
	public void setLightsOn( boolean bLightState )
	{
		if( bLightState )
		{
			CarMod.turnLightsOn();
		}
		else
		{
			CarMod.turnLightsOff();
		}
	}
	
	public void initiateEngineFailure()
	{
		physicsEng.failEngine(true);
	}
	
	public void initiateBrakeFailure()
	{
		physicsEng.failBrakes(true);
	}
	
	public void repairEngineFailure()
	{
		physicsEng.failEngine(false);
	}
	
	public void repairBrakeFailure()
	{
		physicsEng.failBrakes(false);
	}
	
	public void initiateSignalPickupFailure()
	{
		commMod.setSignalPickupFailure(true);
	}
	
	public void repairSignalPickupFailure()
	{
		commMod.setSignalPickupFailure(false);
	}
	
	public void setAnnouncements( String sAnnouncements )
	{
		CarMod.setAnnouncements( sAnnouncements );
	}
	
	public void setAdvertisements( String sAdvert )
	{
		CarMod.setAdvertisements( sAdvert );
	}
	
	public String getStringHeaders()
	{
		StringBuilder sOutput = new StringBuilder();
		sOutput.append("TrainId" + ",");
		sOutput.append("tickLen" + ",");
		sOutput.append("currVelocity" + ",");
		sOutput.append("powerInput" + ",");
		sOutput.append("brakeInput" + ",");
		sOutput.append("Conductor" + ",");
		sOutput.append("Weight" + ",");
		sOutput.append("Height" + ",");
		sOutput.append("Width" + ",");
		sOutput.append("Length" + ",");
		sOutput.append("DoorsOpen" + ",");
		sOutput.append("LightsOn" + ",");
		sOutput.append("NumPassengers");
		
		return sOutput.toString();
	}
	
	public String toString()
	{
		StringBuilder sOutput = new StringBuilder();
		sOutput.append(TrainId + ",");
		sOutput.append(((double)lTickLen)/1000 + ",");
		sOutput.append(currVelocity + ",");
		sOutput.append(physicsEng.powerInput + ",");
		sOutput.append(physicsEng.brakeInput + ",");
		sOutput.append(CarMod.getConductor() + ",");
		sOutput.append(CarMod.getWeight() + ",");
		sOutput.append(CarMod.getHeight() + ",");
		sOutput.append(CarMod.getWidth() + ",");
		sOutput.append(CarMod.getLength() + ",");
		sOutput.append(CarMod.areDoorsOpen() + ",");
		sOutput.append(CarMod.areLightsOn() + ",");
		sOutput.append(CarMod.getNumPassengers());
		
		return sOutput.toString();
	}
	
	public int getTrainID()
	{
		return this.TrainId;
	}
	
	public double getCurrentPower()
	{
		return physicsEng.powerInput;
	}
	
	public double getCurrentAccel()
	{
		return physicsEng.dCurrentAccel;
	}
	
	public double getCurrentBrake()
	{
		return physicsEng.brakeInput;
	}
	
	public String getCurrentEBrake()
	{
		
		if( physicsEng.bEBraking )
		{
			return "ON";
		}
		else
		{
			return "OFF";
		}
	}
	
	public String getDoorState()
	{
		if (CarMod.areDoorsOpen() )
		{
			return "OPEN";
		}
		else
		{
			return "CLOSED";
		}
	}
	
	public String getLightState()
	{
		if( CarMod.areLightsOn() )
		{
			return "ON";
		}
		else
		{
			return "OFF";
		}
	}
	
	public String getAnnouncements()
	{
		return CarMod.getCurrentAnnoucement();
	}
	
	public String getAdverts()
	{
		return CarMod.getCurrentAdvert();
	}
	
	public double getPowerConsumption()
	{
		return -1;
	}
	
	public String getEngineFailure()
	{
		if( physicsEng.getEngineFail() )
		{
			return "FAILURE";
		}
		else
		{
			return "OK";
		}
	}
	
	public String getBrakeFailure()
	{
		if( physicsEng.getBrakeFail() )
		{
			return "FAILURE";
		}
		else
		{
			return "OK";
		}
	}
	
	public String getTransponderFailure()
	{
		if( this.bTransponderFailure )
		{
			return "FAILURE";
		}
		else
		{
			return "OK";
		}
	}
	
	public String getTrackCircuitSignal()
	{
		if( this.commMod.getSignalPickupFailure() )
		{
			return "FAILURE";
		}
		else
		{
			return "OK";
		}
	}
	
	public String getTransponderSignal()
	{
		if( sLastTransponderSignal == null )
		{
			return "NONE";
		}
		else
		{
			return sLastTransponderSignal;
		}
	}
	
	public long getElapsedTime()
	{
		return lElapsedTime;
	}
	
	public String getTrackCircuitFailure()
	{
		if( this.commMod.getSignalPickupFailure() )
		{
			return  new String("FAILURE");
		}
		else
		{
			return  new String("OK");
		}
	}
}
